#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include "mysql.h"

// 这个函数中，我们通过环境变量获得报文的请求方法
// 如果是GET，直接从环境变量中读出客户端上传的参数
// 如果是POST，我们就从管道中读出客户端上传的参数
bool GetQueryString(std::string &query_string)
{
    bool result=false;
    // 从环境变量中获得请求方法（子进程在程序替换之前写好了）
    std::string method = getenv("METHOD");
    // cerr << "Debug Test : " << getenv("METHOD") << endl;
        // 为什么这里要用cerrc测试？
            // 原因是因为当子进程将程序替换成这份代码时，子进程文件打开表中的fd=0和fd=1已经被重定向到管道文件中了
            // 所以如果我们继续用cout标准输出，后面的语句会被输出到管道文件中，我们没有办法在命令行中观测到
            // 而cerr对应的fd=2目前还没有被输出重定向，我们还能在屏幕上观测到
    if("GET" == method){
        // 如果请求方法是method，那么 
        query_string = getenv("QUERY_STRING");
        // cerr << "Debug QUERY_STRING: " << query_string << endl; // 测试代码
        result=true;
    }
    else if("POST" == method){
        // CGI函数如何得知需要从标准输入流中读取多少个字节的数据呢？
            // 通过环境变量告知
        int content_length = atoi(getenv("CONTENT_LENGTH"));//调用atoi函数，将环境变量中字符串类型的content_length转化为整形           
        // cerr << "Debug CONTENT_LENGTH: " << content_length << endl;
        
        // 前面父进程会把正文部分写到管道文件中，下面我们就从管道文件中，将正文中的数据读取出来，存到当前进程的query_string中，以便后续对数据进行处理
        char c = 0;
        while(content_length){
            read(0, &c, 1);// 从fd=0对应的文件中读取1个字符，读到变量c中，注意：这里fd=0经过重定向之后，已经不再是标准输入流，对应的文件也不再是键盘，而是output管道的读端
            query_string.push_back(c);
            content_length--;
        }
        // 到此为止我们就完成了对POST请求报文中用户上传数据的读取
        // cerr << "Post Debug QUERY_STRING: " << query_string << endl; 测试代码
        result=true;
    }
    return result;
}

// 这个是对target字符串进行切分，以sep为界，将target切成两半，左边就是sub1_out，右边的就是sub2_out
bool CutString(const std::string &target, std::string sep, std::string &sub1_out, std::string &sub2_out)
{
    size_t pos = target.find(sep);
    if(pos != std::string::npos){
        sub1_out = target.substr(0, pos); 
        sub2_out = target.substr(pos+sep.size());
        return true;
    }
    return false;
}

// 连接与通信测试：客户端调用mysql提供的接口，连接上mysql服务器
// 然后向mysql语句发送一条插入指令，mysql服务器在后台收到并执行这条指令
bool Insert_Sql(std::string& sql){
    // mysql初始化
    MYSQL *conn = mysql_init(nullptr);

    // 将客户端数据的编码方式设置为utf8，由于服务器端的编码方式大概率也是utf8，这样设置就可以避免乱码了
    mysql_set_character_set(conn, "utf8");
    
    // 链接数据库，如果没连上，输出connect error!
    if(nullptr == mysql_real_connect(conn, "127.0.0.1", "http_test", "www123..", "test", 3306, nullptr, 0)){
    // mysql_real_connect参数介绍
    // 第一个参数就是刚刚创建好的mysql对象，第二个参数是你要连接的数据库所在的主机地址
    // 第三个参数是登录密码，第四个参数是你登录的用户名，第五个参数是你要连接的数据库名称
    // 第六个参数是MySQL 服务器的端口号，第七和第八个参数你不用管了，大部分情况下都是传nullptr和0
        std::cerr << "connect error!" << std::endl;// 因为现在cout已经重定向了，所以我们用cerr来打
        return false;
    }
    std::cerr << "connect success" << std::endl;

    // 构造一条sql语句
    // std::string sql = "insert into user (name, passwd) values ('李四', '12345678')";
    std::cerr << "query : " << sql <<std::endl;
    // 将sql语句发送给数据库服务器端，让它运行这条指令，然后用ret接收命令执行完毕之后返回的结果
    int ret = mysql_query(conn, sql.c_str());
    std::cerr << "result: " << ret << std::endl;
    mysql_close(conn);// 关闭与数据库的链接
    return true;
}

int main()
{
    //test1();
    // 浏览器根据用户在页面上输入的用户和密码，生成对应的HTTP请求报文。然后发送给数据库，让数据库执行
    std::string query_string;
    if(GetQueryString(query_string)){//获得客户端Web上传的数据，将其存入query_string中
        std::cerr << query_string << std::endl;
        //数据处理？
        //name=tom&passwd=111111
        std::string name;
        std::string passwd;

        // 先将将name=xxx和passwd=xxx这俩字符串提出来
        CutString(query_string, "&", name, passwd);

        std::string _name;
        std::string sql_name;
        CutString(name, "=", _name, sql_name);
        // 到这里，_name中存的就是属性名
        // sql_name就是具体的值

        std::string _passwd;
        std::string sql_passwd;
        CutString(passwd, "=", _passwd, sql_passwd);
        // 到这里，_passwd中存的就是属性名
        // sql_passwd就是具体的值

        // 根据我们刚刚提取出来的用户名和密码，构建insert语句
        std::string sql = "insert into user(name, passwd) values (\'";
        sql += sql_name;
        sql += "\',\'";
        sql += sql_passwd;
        sql += "\')";

        //插入数据库
        if(Insert_Sql(sql)){
            // 如果插入成功，我们服务器就给浏览器发一句注册成功，并且给他们返回当前数据库中的所有元素
            std::cout << "<html>";
            std::cout << "<head><meta charset=\"utf-8\"></head>";
            std::cout << "<body><h1>注册成功!</h1></body>";
        }


    }
    return 0;
}